/*
 * JANN - a Java toolkit for creating arbitrary Artificial Neural Networks.
 * 
 * Copyright (c) 2009 Matthijs Snel
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.jann;

import java.util.ArrayList;
import java.util.List;

public class Neuron extends NeuralElement {

	private static final long serialVersionUID = 1628938916661477062L;
	
	protected List<Link> incoming, outgoing;
	protected Function transferF;
	protected transient double	act,
								netInput,
								accrInput,
								netDelta;
	protected transient int callcount;
	
	public Neuron() {
		super();
		act = netInput = accrInput = netDelta = 0;
		incoming = new ArrayList<Link>();
		outgoing = new ArrayList<Link>();
		callcount = 0; 
	}
	
	public Neuron( Function f ) {
		this();
		setTransferFunction(f);
	}
	
	public boolean addInputLink( Link l ) {
		return incoming.add(l);
	}
	
	public boolean addOutputLink( Link l ) {
		return outgoing.add(l);
	}
	
	@Override
	public void backProp( double delta ) {
		//Essentially, do a reverse feedForward: wait until error received from all outgoing links,
		//then update weight and propagate error backwards
		netDelta += delta;
		if ( ++callcount >= outgoing.size() ) {
			fireBackProp();
			netDelta = 0;
			callcount = 0;
		}
	}
	
	@Override
	public void feedForward( double in ) {
		accrInput += in;
		if ( ++callcount >= incoming.size() ) { //wait until input received from all incoming links
			fire();
			accrInput = 0;
			callcount = 0;
		}
	}
	
	public double getActivation() {
		return act;
	}
	
	public List<Link> getIncoming() {
		return incoming;
	}
	
	public List<Link> getOutgoing() {
		return outgoing;
	}
	
	public boolean removeInputLink( Link l ) {
		return incoming.remove(l);
	}
	
	public boolean removeOutputLink( Link l ) {
		return outgoing.remove(l);
	}

	public void setTransferFunction( Function f ) {
		transferF = f;
	}
	
	protected void fire() {
		//weight refers to bias weight here
		netInput = accrInput;
		act = transferF.eval( netInput + weight );
		setChanged();
		notifyObservers();
		for ( Link l : outgoing )
			l.feedForward(act);
	}
	
	protected void fireBackProp() {
		double localDelta = netDelta * getActivationDerivative();
		//don't notify observers, already happens in superclass as part of updateWeight
		//For NEURON essential to FIRST update and THEN propagate
		updateWeight( localDelta );
		for ( Link l : incoming )
			l.backProp(localDelta);
	}
	
	protected double getActivationDerivative() {
		return transferF.deriv(netInput + weight);
	}
	
	@Override
	protected double getSendingActivation() {
		return 1;
	}
}
